1 using System;
2 using System.Diagnostics;
3 using Emgu.CV.Structure;
4
5 namespace Emgu.CV
6 {
7 /// <summary>
8 /// An object recognizer using PCA (Principle Components Analysis)
9 /// </summary>
10 [Serializable]
11 public class EigenObjectRecognizer
12 {
13 private Image<Gray, Single>[] _eigenImages;
14 private Image<Gray, Single> _avgImage;
15 private Matrix<float>[] _eigenValues;
16 private string[] _labels;
17 private double _eigenDistanceThreshold;
18
19 /// <summary>
20 /// Get the eigen vectors that form the eigen space
21 /// </summary>
22 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
23 public Image<Gray, Single>[] EigenImages
24 {
25 get { return _eigenImages; }
26 set { _eigenImages = value; }
27 }
28
29 /// <summary>
30 /// Get or set the labels for the corresponding training image
31 /// </summary>
32 public String[] Labels
33 {
34 get { return _labels; }
35 set { _labels = value; }
36 }
37
38 /// <summary>
39 /// Get or set the eigen distance threshold.
40 /// The smaller the number, the more likely an examined image will be treated as unrecognized object.
41 /// Set it to a huge number (e.g. 5000) and the recognizer will always treated the examined image as one of the known object.
42 /// </summary>
43 public double EigenDistanceThreshold
44 {
45 get { return _eigenDistanceThreshold; }
46 set { _eigenDistanceThreshold = value; }
47 }
48
49 /// <summary>
50 /// Get the average Image.
51 /// </summary>
52 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
53 public Image<Gray, Single> AverageImage
54 {
55 get { return _avgImage; }
56 set { _avgImage = value; }
57 }
58
59 /// <summary>
60 /// Get the eigen values of each of the training image
61 /// </summary>
62 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
63 public Matrix<float>[] EigenValues
64 {
65 get { return _eigenValues; }
66 set { _eigenValues = value; }
67 }
68
69 private EigenObjectRecognizer()
70 {
71 }
72
73
74 /// <summary>
75 /// Create an object recognizer using the specific tranning data and parameters, it will always return the most similar object
76 /// </summary>
77 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
78 /// <param name="termCrit">The criteria for recognizer training</param>
79 public EigenObjectRecognizer(Image<Gray, Byte>[] images, ref MCvTermCriteria termCrit)
80 : this(images, GenerateLabels(images.Length), ref termCrit)
81 {
82 }
83
84 private static String[] GenerateLabels(int size)
85 {
86 String[] labels = new string[size];
87 for (int i = 0; i < size; i++)
88 labels[i] = i.ToString();
89 return labels;
90 }
91
92 /// <summary>
93 /// Create an object recognizer using the specific tranning data and parameters, it will always return the most similar object
94 /// </summary>
95 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
96 /// <param name="labels">The labels corresponding to the images</param>
97 /// <param name="termCrit">The criteria for recognizer training</param>
98 public EigenObjectRecognizer(Image<Gray, Byte>[] images, String[] labels, ref MCvTermCriteria termCrit)
99 : this(images, labels, 0, ref termCrit)
100 {
101 }
102
103 /// <summary>
104 /// Create an object recognizer using the specific tranning data and parameters
105 /// </summary>
106 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
107 /// <param name="labels">The labels corresponding to the images</param>
108 /// <param name="eigenDistanceThreshold">
109 /// The eigen distance threshold, (0, ~1000].
110 /// The smaller the number, the more likely an examined image will be treated as unrecognized object.
111 /// If the threshold is < 0, the recognizer will always treated the examined image as one of the known object.
112 /// </param>
113 /// <param name="termCrit">The criteria for recognizer training</param>
114 public EigenObjectRecognizer(Image<Gray, Byte>[] images, String[] labels, double eigenDistanceThreshold, ref MCvTermCriteria termCrit)
115 {
116 Debug.Assert(images.Length == labels.Length, "The number of images should equals the number of labels");
117 Debug.Assert(eigenDistanceThreshold >= 0.0, "Eigen-distance threshold should always >= 0.0");
118
119 CalcEigenObjects(images, ref termCrit, out _eigenImages, out _avgImage);
120
121 /*
122 _avgImage.SerializationCompressionRatio = 9;
123
124 foreach (Image<Gray, Single> img in _eigenImages)
125 //Set the compression ration to best compression. The serialized object can therefore save spaces
126 img.SerializationCompressionRatio = 9;
127 */
128
129 _eigenValues = Array.ConvertAll<Image<Gray, Byte>, Matrix<float>>(images,
130 delegate(Image<Gray, Byte> img)
131 {
132 return new Matrix<float>(EigenDecomposite(img, _eigenImages, _avgImage));
133 });
134
135 _labels = labels;
136
137 _eigenDistanceThreshold = eigenDistanceThreshold;
138 }
139
140 #region static methods
141 /// <summary>
142 /// Caculate the eigen images for the specific traning image
143 /// </summary>
144 /// <param name="trainingImages">The images used for training </param>
145 /// <param name="termCrit">The criteria for tranning</param>
146 /// <param name="eigenImages">The resulting eigen images</param>
147 /// <param name="avg">The resulting average image</param>
148 public static void CalcEigenObjects(Image<Gray, Byte>[] trainingImages, ref MCvTermCriteria termCrit, out Image<Gray, Single>[] eigenImages, out Image<Gray, Single> avg)
149 {
150 int width = trainingImages[0].Width;
151 int height = trainingImages[0].Height;
152
153 IntPtr[] inObjs = Array.ConvertAll<Image<Gray, Byte>, IntPtr>(trainingImages, delegate(Image<Gray, Byte> img) { return img.Ptr; });
154
155 if (termCrit.max_iter <= 0 || termCrit.max_iter > trainingImages.Length)
156 termCrit.max_iter = trainingImages.Length;
157
158 int maxEigenObjs = termCrit.max_iter;
159
160 #region initialize eigen images
161 eigenImages = new Image<Gray, float>[maxEigenObjs];
162 for (int i = 0; i < eigenImages.Length; i++)
163 eigenImages[i] = new Image<Gray, float>(width, height);
164 IntPtr[] eigObjs = Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; });
165 #endregion
166
167 avg = new Image<Gray, Single>(width, height);
168
169 CvInvoke.cvCalcEigenObjects(
170 inObjs,
171 ref termCrit,
172 eigObjs,
173 null,
174 avg.Ptr);
175 }
176
177 /// <summary>
178 /// Decompose the image as eigen values, using the specific eigen vectors
179 /// </summary>
180 /// <param name="src">The image to be decomposed</param>
181 /// <param name="eigenImages">The eigen images</param>
182 /// <param name="avg">The average images</param>
183 /// <returns>Eigen values of the decomposed image</returns>
184 public static float[] EigenDecomposite(Image<Gray, Byte> src, Image<Gray, Single>[] eigenImages, Image<Gray, Single> avg)
185 {
186 return CvInvoke.cvEigenDecomposite(
187 src.Ptr,
188 Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; }),
189 avg.Ptr);
190 }
191 #endregion
192
193 /// <summary>
194 /// Given the eigen value, reconstruct the projected image
195 /// </summary>
196 /// <param name="eigenValue">The eigen values</param>
197 /// <returns>The projected image</returns>
198 public Image<Gray, Byte> EigenProjection(float[] eigenValue)
199 {
200 Image<Gray, Byte> res = new Image<Gray, byte>(_avgImage.Width, _avgImage.Height);
201 CvInvoke.cvEigenProjection(
202 Array.ConvertAll<Image<Gray, Single>, IntPtr>(_eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; }),
203 eigenValue,
204 _avgImage.Ptr,
205 res.Ptr);
206 return res;
207 }
208
209 /// <summary>
210 /// Get the Euclidean eigen-distance between <paramref name="image"/> and every other image in the database
211 /// </summary>
212 /// <param name="image">The image to be compared from the training images</param>
213 /// <returns>An array of eigen distance from every image in the training images</returns>
214 public float[] GetEigenDistances(Image<Gray, Byte> image)
215 {
216 using (Matrix<float> eigenValue = new Matrix<float>(EigenDecomposite(image, _eigenImages, _avgImage)))
217 return Array.ConvertAll<Matrix<float>, float>(_eigenValues,
218 delegate(Matrix<float> eigenValueI)
219 {
220 return (float)CvInvoke.cvNorm(eigenValue.Ptr, eigenValueI.Ptr, Emgu.CV.CvEnum.NORM_TYPE.CV_L2, IntPtr.Zero);
221 });
222 }
223
224 /// <summary>
225 /// Given the <paramref name="image"/> to be examined, find in the database the most similar object, return the index and the eigen distance
226 /// </summary>
227 /// <param name="image">The image to be searched from the database</param>
228 /// <param name="index">The index of the most similar object</param>
229 /// <param name="eigenDistance">The eigen distance of the most similar object</param>
230 /// <param name="label">The label of the specific image</param>
231 public void FindMostSimilarObject(Image<Gray, Byte> image, out int index, out float eigenDistance, out String label)
232 {
233 float[] dist = GetEigenDistances(image);
234
235 index = 0;
236 eigenDistance = dist[0];
237 for (int i = 1; i < dist.Length; i++)
238 {
239 if (dist[i] < eigenDistance)
240 {
241 index = i;
242 eigenDistance = dist[i];
243 }
244 }
245 label = Labels[index];
246 }
247
248 /// <summary>
249 /// Try to recognize the image and return its label
250 /// </summary>
251 /// <param name="image">The image to be recognized</param>
252 /// <returns>
253 /// String.Empty, if not recognized;
254 /// Label of the corresponding image, otherwise
255 /// </returns>
256 public String Recognize(Image<Gray, Byte> image)
257 {
258 int index;
259 float eigenDistance;
260 String label;
261 FindMostSimilarObject(image, out index, out eigenDistance, out label);
262
263 return (_eigenDistanceThreshold <= 0 || eigenDistance < _eigenDistanceThreshold ) ? _labels[index] : String.Empty;
264 }
265 }
266 }